package wave

import (
	"fmt"
	"os"
	"unsafe"
)

// IsLittleEndian ...
func IsLittleEndian() bool {
	var i int32 = 0x01020304
	u := unsafe.Pointer(&i)
	pb := (*byte)(u)
	b := *pb
	return (b == 0x04)
}

// Header wave file header info.
type Header struct {
	sampleFreq   float64
	sampleCount  uint32
	numChannels  uint32
	reverseBytes bool
}

// NewHeader new a Header.
func NewHeader() *Header {
	return &Header{}
}

// Read read from a *os.File.
func (header *Header) Read(file *os.File) {
	reader := NewTag(file)
	reader.Read4ByteTag()
	if string(reader.tag) == "RIFF" {
		header.reverseBytes = false
	} else if string(reader.tag) == "RIFX" {
		header.reverseBytes = true
	} else {
		panic("wave header expected RIFF or RIFX, got " + string(reader.tag))
	}

	if !IsLittleEndian() {
		header.reverseBytes = !header.reverseBytes
	}
	reader.swap = header.reverseBytes

	riffChunkSize := reader.ReadUint32()
	reader.Expect4ByteTag([]byte("WAVE"))
	var riffChunkRead uint32 = 0
	riffChunkRead += 4

	reader.Read4ByteTag()
	riffChunkRead += 4
	for string(reader.tag) != "fmt " {
		var fillerSize uint32 = reader.ReadUint32()
		riffChunkRead += 4
		fillerSizeBytes := make([]byte, fillerSize)
		n, err := reader.file.Read(fillerSizeBytes)
		if n != int(fillerSize) {
			panic(err)
		}
		riffChunkRead += fillerSize
		reader.Read4ByteTag()
		riffChunkRead += 4
	}
	if string(reader.tag) != "fmt " {
		panic("expected fmt ")
	}
	subchunk1Size := reader.ReadUint32()
	audioFormat := reader.ReadUint16()
	header.numChannels = uint32(reader.ReadUint16())
	sampleRate := reader.ReadUint32()
	byteRate := reader.ReadUint32()
	blockAlign := reader.ReadUint16()
	bitsPerSample := reader.ReadUint16()

	header.sampleFreq = float64(sampleRate)

	var fmtChunkRead uint32 = 16
	if audioFormat == uint16(1) {
		if subchunk1Size < uint32(16) {
			panic("expect pcm format data to have fmt chunk of at least size 16.")
		}

	} else if audioFormat == 0xFFFE {
		var extraSize uint16 = reader.ReadUint16()
		if subchunk1Size < uint32(40) || extraSize < uint16(22) {
			panic("malformed WAVE_FORMAT_EXTENSIBLE format data")
		}
		reader.ReadUint16()
		reader.ReadUint32()
		var (
			guid1 = reader.ReadUint32()
			guid2 = reader.ReadUint32()
			guid3 = reader.ReadUint32()
			guid4 = reader.ReadUint32()
		)
		fmtChunkRead = 40
		if guid1 != 0x00000001 || guid2 != 0x00100000 || guid3 != 0xAA000080 || guid4 != 0x719B3800 {
			panic("unsupported WAVE_FORMAT_EXTENSIBLE format.")
		}
	} else {
		panic("can read only PCM data, format id in file is " + string(audioFormat))
	}
	subchunk1SizeBytes := make([]byte, subchunk1Size-fmtChunkRead)
	n, err := reader.file.Read(subchunk1SizeBytes)
	if n != int(subchunk1Size-fmtChunkRead) {
		panic(err)
	}

	if header.numChannels == uint32(0) {
		panic("no channels present")
	}

	if bitsPerSample != uint16(16) {
		panic(fmt.Sprintf("unsupported bits_per_sample = %d", bitsPerSample))
	}

	if byteRate != sampleRate*uint32(bitsPerSample)/uint32(8)*uint32(header.numChannels) {
		panic(fmt.Sprintf("unexpected byte rate %d vs. %d * %d * %d", byteRate, sampleRate, bitsPerSample/8, header.numChannels))
	}

	if blockAlign != uint16(header.numChannels)*bitsPerSample/uint16(8) {
		panic(fmt.Sprintf("unexpected block_align: %d vs. %d * %d", blockAlign, header.numChannels, bitsPerSample/8))
	}

	riffChunkRead += 4 + subchunk1Size
	reader.Read4ByteTag()
	riffChunkRead += 4

	for string(reader.tag) != "data" {
		chunkSz := reader.ReadUint32()
		if chunkSz != uint32(4) && string(reader.tag) == "fact" {
			panic("expected fact chunk to be 4 bytes long.")
		}
		chunkSzBytes := make([]byte, chunkSz)
		n, err := reader.file.Read(chunkSzBytes)
		if n != int(chunkSz) {
			panic(err)
		}
		riffChunkRead += 4 + chunkSz
		reader.Read4ByteTag()
		riffChunkRead += 4
	}
	if string(reader.tag) != "data" {
		panic("expected to read 'data'")
	}

	dataChunkSize := reader.ReadUint32()
	riffChunkRead += 4
	var isStramMode bool = riffChunkSize == 0 || riffChunkSize == 0xFFFFFFFF || dataChunkSize == 0 || dataChunkSize == 0xFFFFFFFF || dataChunkSize == 0x7FFFF000
	if isStramMode {
		panic("unexpected stream mode")
	}
	header.sampleCount = uint32(dataChunkSize / uint32(blockAlign))
}

//func (header *Header) IsStreamed() bool {
//	return header.sampleCount < 0
//}
//
//func (wi *Header) Duration() float64 {
//	return float64(wi.sampleCount) / wi.sampleFreq
//}
//

// BlockAlign wave data block align.
func (header *Header) BlockAlign() uint32 {
	return 2 * header.numChannels
}

// DataBytes one sample takes how many bytes.
func (header *Header) DataBytes() uint32 {
	return header.sampleCount * header.BlockAlign()
}
